import Foundation
import SQLite3

public protocol MPConvertible {
    
    init()
                    
    func mp_propertyMapper() -> [String : String]?
        
    mutating func mp_propertyAssign(key : String, value : Any)
    
    mutating func mp_didConvert()
    
}

public extension MPConvertible {
                
    @discardableResult
    static func mp_model(_ dic : [String : Any]) -> Self {
        var _model = Self.init()
        let _info = _model.mp_convertInfo()
        if let _point = _info.point {
            var modelInfo : MPConvertibleModelInfo? = _info.modelInfo
            while let _modelInfo = modelInfo {
                for typeInfo in _modelInfo.propertyList {
                    if typeInfo.ignoreProperty == false {
                        if let jsonValue = dic[typeInfo.replaceKey ?? typeInfo.propertyKey] ?? dic[typeInfo.propertyKey] {
                            if let value = mp_model_convert(jsonValue, info: typeInfo) {
                                _point.advanced(by: typeInfo.offset).mp_set(value, typeInfo.propertyType)
                                _model.mp_propertyAssign(key: typeInfo.replaceKey ?? typeInfo.propertyKey, value: value)
                            }
                        }
                    }
                }
                modelInfo = _modelInfo.superInfo
            }
        }
        _model.mp_didConvert()
        return _model
    }
    
    static func mp_models(_ array : [[String : Any]]) -> [Self] {
        var list : [Self] = []
        for dic in array {
            list.append(Self.mp_model(dic))
        }
        return list
    }

    func mp_json() -> [String : Any]{
        var _model = self
        let _info = _model.mp_convertInfo()
        var result : [String : Any] = [:]
        if let _point = _info.point {
            var modelInfo : MPConvertibleModelInfo? = _info.modelInfo
            while let _modelInfo = modelInfo {
                for typeInfo in _modelInfo.propertyList {
                    if let jsonValue = mp_json_convert(_point.advanced(by: typeInfo.offset).mp_get(typeInfo.propertyType), info: typeInfo) {
                        result[typeInfo.replaceKey ?? typeInfo.propertyKey] = jsonValue
                    }
                }
                modelInfo = _modelInfo.superInfo
            }
        }
        return result
    }
    
    func mp_value(for key : String) -> Any? {
        var _model = self
        let _info = _model.mp_convertInfo()
        if let _point = _info.point {
            var modelInfo : MPConvertibleModelInfo? = _info.modelInfo
            while let _modelInfo = modelInfo {
                if let typeInfo = _modelInfo.propertyList.first(where: { _typeInfo in
                    return _typeInfo.propertyKey == key
                }) {
                    return mp_json_convert(_point.advanced(by: typeInfo.offset).mp_get(typeInfo.propertyType), info: typeInfo)
                }
                modelInfo = _modelInfo.superInfo
            }
        }
        return nil
    }
    
    func mp_propertyList() -> [String : Any] {
        var propertyList : [String : Any] = [:]
        var modelInfo : MPConvertibleModelInfo?
        if let displayStyle = Mirror(reflecting: self).displayStyle {
            switch displayStyle {
                case .struct:
                    modelInfo = MPConvertibleMetaData.reloadModelInfo(Self.self, displayStyle: .struct_style, obj: self)
                case .class:
                    modelInfo = MPConvertibleMetaData.reloadModelInfo(Self.self, displayStyle: .class_style, obj: self)
                default:
                    modelInfo = nil
            }
        }
        while modelInfo != nil {
            for propertyInfo in modelInfo?.propertyList ?? [] {
                propertyList[propertyInfo.propertyKey] = propertyInfo.propertyType
            }
            modelInfo = modelInfo?.superInfo
        }
        return propertyList
    }
    
    func mp_propertyMapper() -> [String : String]? {
        return nil
    }
    
    mutating func mp_propertyAssign(key : String, value : Any) {}
    
    mutating func mp_didConvert() {}
    
}

private extension MPConvertible {
    
    mutating func mp_convertInfo() -> (point: UnsafeMutableRawPointer?, modelInfo: MPConvertibleModelInfo?) {
        if let displayStyle = Mirror(reflecting: self).displayStyle {
            switch displayStyle {
                case .struct:
                    let mutableTypedPointer = withUnsafeMutablePointer(to: &self) {
                        return UnsafeRawPointer($0).bindMemory(to: Int8.self, capacity: MemoryLayout<Self>.stride)
                    }
                return (UnsafeMutableRawPointer(mutating: mutableTypedPointer), MPConvertibleMetaData.reloadModelInfo(Swift.type(of: self), displayStyle: .struct_style, obj: self))
                case .class:
                    let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
                    let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self, capacity: MemoryLayout<Self>.stride)
                return (UnsafeMutableRawPointer(mutableTypedPointer), MPConvertibleMetaData.reloadModelInfo(Swift.type(of: self), displayStyle: .class_style, obj: self))
                default:
                    return (nil, nil)
            }
        }
        return (nil, nil)
    }
    
}
